package org.zebra.logging.interceptor;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.zebra.common.util.UriWhiteUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.IntStream;

/**
 * @author zhanghongbin
 */
@Slf4j
public class HttpLogInterceptor implements HandlerInterceptor {

    private JSONConfig config = JSONConfig.create().setDateFormat("yyyy-MM-dd HH:mm:ss");

    private PathMatcher pathMatcher;
    private Set<String> uriWhiteSet;

    public HttpLogInterceptor(PathMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
        this.uriWhiteSet = new HashSet<String>(UriWhiteUtil.getDefaultUriWhite());
        this.uriWhiteSet.add("/**/*.png");
        this.uriWhiteSet.add("/**/*.css");
        this.uriWhiteSet.add("/**/*.svg");
        this.uriWhiteSet.add("/**/*.js");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        if (ServletUtil.isMultipart(request)) {
            return true;
        }
        String uri = request.getServletPath();
        boolean flag = this.uriWhiteSet.stream().anyMatch(excludeUrl -> pathMatcher.matchStart(excludeUrl, uri));
        if (flag) return true;

        String className = "";
        if (handler instanceof HandlerMethod) {
            Method method = ((HandlerMethod) handler).getMethod();
            className = method.getDeclaringClass().getName();
            String lineNumber = getLineNumber(method.getDeclaringClass(), method);
            className = className + "(" + method.getDeclaringClass().getSimpleName() + ".java:" + lineNumber + ")方法名:"
                    + method.getName();
        }
        String url = request.getMethod() + " " + request.getRequestURI();
        String headerStr = JSONUtil.toJsonStr(ServletUtil.getHeaderMap(request));
        if (isJsonRequest(request)) {
            String jsonParam = ServletUtil.getBody(request);
            log.info(
                    "\n开始请求 => URL:[{}]\n请求报头:[{}]\n参数:[{}]\nController路径:[{}]\n",
                    url,
                    headerStr,
                    jsonParam,
                    className);
        } else {
            if (!isSseRequest(request)) {
                Map<String, String[]> parameterMap = request.getParameterMap();
                if (MapUtil.isNotEmpty(parameterMap)) {
                    String parameters = JSONUtil.toJsonStr(parameterMap);
                    log.info(
                            "\n开始请求 => URL:[{}]\n请求报头:[{}]\n参数:[{}]\nController路径:[{}]\n",
                            url,
                            headerStr,
                            parameters,
                            className);

                } else {
                    log.info("\n开始请求 => URL:[{}]\n请求报头:[{}]\nController路径:[{}]\n", url, headerStr, className);
                }
            }
        }
        return true;
    }

    /**
     * 代码来自芋道
     *
     * @param clazz
     * @param method
     * @return
     */
    private String getLineNumber(Class clazz, Method method) {
        String path = ResourceUtil.getResource(null, clazz).getPath().replace("/target/classes/", "/src/main/java/")
                + clazz.getSimpleName() + ".java";
        if (!FileUtil.exist(path)) {
            return "";
        }
        List<String> clazzContents = FileUtil.readUtf8Lines(path);
        Optional<Integer> lineNumber = IntStream.range(0, clazzContents.size())
                .filter(i -> clazzContents.get(i).contains(" " + method.getName() + "(")) // 简单匹配，不考虑方法重名
                .mapToObj(i -> i + 1) // 行号从 1 开始
                .findFirst();
        if (!lineNumber.isPresent()) {
            return "";
        }
        return lineNumber.get().toString();
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        if (ServletUtil.isMultipart(request)) {
            return;
        }
        String uri = request.getServletPath();
        boolean flag = UriWhiteUtil.getDefaultUriWhite().stream()
                .anyMatch(excludeUrl -> pathMatcher.matchStart(excludeUrl, uri));
        if (flag) return;
        if (!isSseRequest(request)) {
            String url = request.getMethod() + " " + request.getRequestURI();
            Object result = request.getAttribute("$result");
            // Object result = ThreadLocalUtil.$get().getObj("$result");
            String responseContent = "";
            if (result != null) {
                responseContent = JSONUtil.toJsonStr(result, config);
            }
            String headerStr = JSONUtil.toJsonStr(ServletUtil.getHeadersMap(response));
            log.info("\n结束请求 => URL:[{}]\n响应报头:[{}]\n响应内容:[{}]\n", url, headerStr, responseContent);
        }
    }

    private boolean isSseRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType == null) {
            contentType = request.getHeader("accept");
        }
        if (contentType != null) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.TEXT_EVENT_STREAM_VALUE);
        }
        return false;
    }

    private boolean isJsonRequest(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null) {
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
        }
        return false;
    }
}
